package cn.taskservice.redis.lock.aspect;

import cn.hutool.core.util.StrUtil;
import cn.taskservice.redis.lock.annotation.RedisLock;
import cn.taskservice.redis.lock.enums.LockType;
import cn.taskservice.redis.lock.enums.LockfailStrategy;
import cn.taskservice.redis.lock.service.FallbackHandle;
import cn.taskservice.redis.lock.util.RedissonUtil;
import cn.taskservice.redis.lock.util.SpELParserUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

@Slf4j
@Aspect
@Component
public class RedisLockAspect {
    @Autowired
    private RedissonUtil redissonUtil;
    @Autowired
    private SpELParserUtil spelParser;
    @Autowired
    private ApplicationContext applicationContext;

    @Around("@annotation(redisLock)")
    public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        String lockKey = spelParser.parse(redisLock.key(), method, joinPoint.getArgs());
        LockType lockType = redisLock.lockType();
        TimeUnit timeUnit = redisLock.timeUnit();
        LockfailStrategy failStrategy = redisLock.failStrategy();
        long waitTime = redisLock.waitTime();
        long leaseTime = redisLock.leaseTime();
        int tryNum = redisLock.tryNum();
        RLock lock = null;
        /*****尝试获取锁****/
        while (tryNum-- > 0) {
            lock = redisLock.autoRenewal() ?
                    redissonUtil.tryLockWithWatchdog(lockType, lockKey, waitTime, timeUnit) :
                    redissonUtil.tryLockWithInstance(lockType, lockKey, waitTime, leaseTime, timeUnit);
            if (lock != null) break;
            log.warn("线程 [{}] 获取锁失败，尝试剩余次数: {}", Thread.currentThread().getName(), tryNum);
            Thread.sleep(300); // 可配置
        }
        /******获取锁失败，选择是否降级*****/
        if (lock == null) {
            if (failStrategy == LockfailStrategy.FAIL_FAST) {
                throw new RuntimeException("获取Redis分布式锁失败，key=" + lockKey);
            } else {
                log.info("线程 [{}] 获取锁失败,执行降级策略 [{}]，执行方法 [{}]", Thread.currentThread().getName(), lockKey, method.getName());
                return fallbackHandle(joinPoint, redisLock);
            }
        }
        try {
            log.info("线程 [{}] 成功获取锁 [{}]，执行方法 [{}]", Thread.currentThread().getName(), lockKey, method.getName());
            return joinPoint.proceed();
        } finally {
            redissonUtil.unlock(lock);
            log.info("线程 [{}] 释放锁 [{}]", Thread.currentThread().getName(), lockKey);
        }
    }

    /**
     * 自定义降级逻辑
     * @param joinPoint
     * @param redisLock
     * @return
     */
    private Object fallbackHandle(ProceedingJoinPoint joinPoint, RedisLock redisLock) {
        String beanName = redisLock.beanName();
        if (beanName == null || beanName.trim().isEmpty()) {
            throw new RuntimeException("锁获取失败，且未配置降级处理 beanName");
        }
        try {
            Object instance = applicationContext.getBean(beanName);
            if (!(instance instanceof FallbackHandle)) {
                throw new RuntimeException("分布式锁降级处理的 Bean 必须实现 FallbackHandle 接口，beanName=" + beanName);
            }
            return ((FallbackHandle) instance).fallback(joinPoint.getArgs());
        } catch (Throwable e) {
            log.error("分布式锁降级处理失败，beanName=" + beanName + "，错误信息：" + e.getMessage(), e);
            throw new RuntimeException("分布式锁降级处理异常", e);
        }
    }
}